package com.hxy.network.core.webScoket;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;

/**
 * @description:
 * @program: component_code_integration
 * @author: HCY
 * @create: 2022/04/25 13:59
 * 安全考虑需要 鉴权:
 * 类似于sid参数，将token放到请求路径中，然后在onOpen中对token进行鉴权；
 * 建立链接后前端在onOpen中立即发送消息将token传递到后端，后端获取到token消息后对其进行鉴权并记录，以后每次消息交互时都要判断一下鉴权记录是否通过。（这样做是为了保证建立成功但未鉴权的链接不能正常收发消息)
 * 通过子协议传递鉴权信息，由于js的websocket不能设置header信息，但是可以通过设置子协议（new WebSocket(url [, protocols]);），而子协议实际上是放到header对应的Sec-WebSocket-Protocol中的
 * 注：spring-boot-starter-websocket 低层是基于Tomcat的，性能回差。
      netty-websocket-spring-boot-starter 基于netty实现的 性能交高
 */
@ServerEndpoint("/socket/{userId}")
@Component
@Slf4j
public class WebSocketServer {

    private String userId;

    private Session session;

    private final static String ping = "ping";

    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        log.info("WebSocket onOpen userId:{}", userId);
        this.userId = userId;
        this.session = session;
        WebSocketHolder.put(userId, this);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        log.info("WebSocket onClose userId:{}", userId);
        WebSocketHolder.remove(userId, this);
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("WebSocket onMessage message: {}", message);
        // 不是心跳检测消息，则进行逻辑处理
        if (ping.equals(message)) {
            sendMessage("pong");
        }
    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket onError");
    }

    /**
     * 实现服务器主动推送
     * @param message
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("消息发送失败: {}", e.getMessage(), e);
        }
    }
    /**
     * 实现服务器主动推送
     * MessageVo 发送实体消息
     */
    private <T> void sendMessage(T messageVo) throws IOException {
        this.session.getBasicRemote().sendText(JSONObject.toJSONString(messageVo));
    }
    /**
     * 实现服务器主动推送,推送给指定的一个人
     *
     */
    public void sendMessageToOthers(String message,String toUserId) {
        if(StringUtils.isEmpty(message)|| StringUtils.isEmpty(toUserId)){
            return;
        }
        Set<WebSocketServer> set = WebSocketHolder.get(toUserId);
        if (CollectionUtils.isEmpty(set)) {
            log.error("用户"+ toUserId +",不在线！");
            return;
        }
        set.stream().forEach(socketServer -> {
            socketServer.sendMessage(message);
        });
    }


    /**
     * 关闭
     */
    public void close() {
        try {
            this.session.close();
        } catch (IOException e) {
            log.error("session 关闭失败: {}", e.getMessage(), e);
        }
    }
}
